package storage

import (
	"github.com/nuttech/bell"
	"github.com/stretchr/testify/mock"
	v1alpha12 "gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/clientset"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/clientset/mocks"
	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/rest"
	"reflect"
	"testing"
)

func TestStorageController_Start(t *testing.T) {
	type fields struct {
		triggers   map[string]v1alpha12.Trigger
		templates  map[string]v1alpha12.Template
		scopes     map[string]v1alpha12.ScopeSpec
		namespaces map[string]corev1.Namespace
		senderMode bool
	}
	type args struct {
		withInit   bool
		senderMode bool
	}
	tests := []struct {
		name   string
		fields fields
		args   args
	}{
		{
			name: "ok",
			args: args{
				withInit:   true,
				senderMode: true,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()
			var storageController = &StorageController{}
			storageController.Start(tt.args.senderMode, tt.args.senderMode)
			storageController.Stop()
		})
	}
}

func TestStorageController_UpdateTrigger(t *testing.T) {
	tests := []struct {
		name    string
		message bell.Message
		trigger v1alpha12.Trigger
	}{
		{
			name: "ok",
			trigger: v1alpha12.Trigger{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "trtr",
					Namespace: "123",
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {

			logs.Logger = zap.NewNop()
			var storageController = &StorageController{}
			storageController.Start(false, false)
			storageController.UpdateTrigger(bell.Message{Value: tt.trigger})
			res, ok := storageController.triggers[tt.trigger.GetNamespacedName()]
			if !ok {
				t.Errorf("Trigger was not found")
			}
			if !reflect.DeepEqual(tt.trigger, res) {
				t.Errorf("Unexpected trigger value")
			}

			storageController.DeleteTrigger(bell.Message{Value: res})
			if _, ok := storageController.triggers[tt.trigger.GetNamespacedName()]; ok {
				t.Errorf("Trigger has not been deleted")
			}
		})
	}
}

func TestStorageController_UpdateTemplate(t *testing.T) {
	tests := []struct {
		name     string
		message  bell.Message
		template v1alpha12.Template
	}{
		{
			name: "ok",
			template: v1alpha12.Template{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "template",
					Namespace: "123",
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()
			var storageController = &StorageController{}
			storageController.Start(false, false)
			storageController.UpdateTemplate(bell.Message{Value: tt.template})
			res, ok := storageController.templates[tt.template.GetNamespacedName()]
			if !ok {
				t.Errorf("Template was not found")
			}
			if !reflect.DeepEqual(tt.template, res) {
				t.Errorf("Unexpected template value")
			}

			storageController.DeleteTemplate(bell.Message{Value: res})
			if _, ok := storageController.templates[tt.template.GetNamespacedName()]; ok {
				t.Errorf("Template has not been deleted")
			}
		})
	}
}

func TestStorageController_UpdateScope(t *testing.T) {
	tests := []struct {
		name       string
		message    bell.Message
		scope      v1alpha12.Scope
		senderMode bool
	}{
		{
			name: "ok",
			scope: v1alpha12.Scope{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "scope",
					Namespace: "123",
				},
			},
			senderMode: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()
			var storageController = &StorageController{}
			storageController.Start(false, tt.senderMode)
			storageController.UpdateScope(bell.Message{Value: tt.scope})
			res, ok := storageController.scopes[tt.scope.GetNamespacedName()]
			if !ok {
				t.Errorf("Scope was not found")
			}
			if !reflect.DeepEqual(tt.scope, res) {
				t.Errorf("Unexpected scope value")
			}

			storageController.GetScopeItems("")

			storageController.DeleteScope(bell.Message{Value: tt.scope})
			if _, ok := storageController.scopes[tt.scope.GetNamespacedName()]; ok {
				t.Errorf("Scope has not been deleted")
			}
		})
	}
}

func TestStorageController_UpdateNamespace(t *testing.T) {
	tests := []struct {
		name       string
		message    bell.Message
		namespace  corev1.Namespace
		senderMode bool
	}{
		{
			name: "ok",
			namespace: corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "namespace",
					Namespace: "123",
				},
			},
			senderMode: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()
			var storageController = &StorageController{}
			storageController.Start(false, tt.senderMode)
			storageController.UpdateNamespace(bell.Message{Value: tt.namespace})
			res, ok := storageController.namespaces[tt.namespace.ObjectMeta.Name]
			if !ok {
				t.Errorf("Namespace was not found")
			}
			if !reflect.DeepEqual(tt.namespace, res) {
				t.Errorf("Unexpected namespace value")
			}

			storageController.DeleteNamespace(bell.Message{Value: res})
			if _, ok := storageController.namespaces[tt.namespace.Namespace]; ok {
				t.Errorf("Namespace has not been deleted")
			}
		})
	}
}

func TestStorageController_GetResources(t *testing.T) {
	var storageController = &StorageController{}
	storageController.Start(false, false)
	storageController.GetScopes()
	storageController.GetTemplates()
	storageController.GetTriggers()
	storageController.GetNamespaces()
	storageController.GetScopeItems("validate")
}

func TestStorageController_CheckTriggerTemplatePair(t *testing.T) {
	tests := []struct {
		name      string
		triggers  map[string]v1alpha12.Trigger
		templates map[string]v1alpha12.Template
	}{
		{
			name: "ok",
			triggers: map[string]v1alpha12.Trigger{
				"triggerName": {Spec: v1alpha12.TriggerSpec{
					CreationConfigs: []v1alpha12.CreationConfig{
						{
							TemplateRefs: []string{"ref", "rrref"},
						},
					},
				}},
			},
			templates: map[string]v1alpha12.Template{"ref": {}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()

			r := &StorageController{
				triggers:  tt.triggers,
				templates: tt.templates,
			}
			r.CheckTriggerTemplatePair()
		})
	}
}

func TestStorageController_InitStorage(t *testing.T) {
	bell.Listen(operator.SyncAddNewObjectEvent, func(message bell.Message) {})
	defer bell.Remove(operator.SyncAddNewObjectEvent)

	startKbltCreator := operator.KbltCreator
	startKbltMutator := operator.KbltMutator
	startKbltValidator := operator.KbltValidator

	defer func() {
		operator.KbltCreator = startKbltCreator
		operator.KbltMutator = startKbltMutator
		operator.KbltValidator = startKbltValidator
	}()

	operator.KbltCreator = "true"
	operator.KbltMutator = "true"
	operator.KbltValidator = "true"

	logs.Logger = zap.NewNop()

	restCli := mocks.NewInterface(t)
	restCli.On("Get", mock.Anything).Return(&rest.Request{}).Maybe()

	triggerCli := mocks.NewTriggerInterface(t)
	triggerCli.On("List", mock.Anything, mock.Anything).
		Return(&v1alpha12.TriggerList{Items: []v1alpha12.Trigger{{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "trig",
				Namespace: "123",
			}}}}, nil,
		).Maybe()

	templCli := mocks.NewTemplateInterface(t)
	templCli.On("List", mock.Anything, mock.Anything).
		Return(&v1alpha12.TemplateList{Items: []v1alpha12.Template{{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "templ",
				Namespace: "123"},
		}}}, nil).Maybe()

	scopeCli := mocks.NewScopeInterface(t)
	scopeCli.On("List", mock.Anything, mock.Anything).
		Return(&v1alpha12.ScopeList{Items: []v1alpha12.Scope{{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "scope",
				Namespace: "123"},
		}}}, nil).Maybe()

	triggerInsCli := mocks.NewTriggerInstanceInterface(t)

	operator.Cl = &clientset.V1Alpha1Client{
		TemplateClient:        templCli,
		TriggerClient:         triggerCli,
		ScopeClient:           scopeCli,
		TriggerInstanceClient: triggerInsCli,
		RestClient:            restCli,
	}

	r := &StorageController{}
	r.Start(true, true)
	r.Stop()
	operator.Cl = nil
}
